import time
import random
import functools
import math
import logging
from operator import add

'''
TODO: Docstring
'''
def sigmoidal_weight_filter(weight, maxweight):
    return maxweight/(1 + math.exp(-(maxweight*math.e)*weight + (maxweight*math.e)))

def scale_timecourse(timecourse):
    if float(max(timecourse)) == 0.00:
        return timecourse
    return [val/max(timecourse) for val in timecourse]

def list_to_matrix(list,rows,cols):
    gen = (val for val in list)
    matrix = []
    for x in range(rows):
        row = []
        for y in range(cols):
            row.append(next(gen))
        matrix.append(row)
    return matrix

def print_list_as_matrix(list,rows,cols):
    gen = (val for val in list)
    matrix = []
    for x in range(rows):
        row = []
        for y in range(cols):
            row.append(next(gen))
        print row
    return

def log_list_as_matrix(list,rows,cols):
    gen = (val for val in list)
    matrix = []
    for x in range(rows):
        row = []
        for y in range(cols):
            row.append(next(gen))
        logging.debug(row)
    return

def list_duplicates_of(seq,item):
    start_at = -1
    locs = []
    while True:
        try:
            loc = seq.index(item,start_at+1)
        except ValueError:
            break
        else:
            locs.append(loc)
            start_at = loc
    return locs

def list_accumulate(l1,l2):
    for i,item in enumerate(l1):
        l2[i] = l2[i] + item
    return l2[:]

def list_subtract(l1,l2):
    for i,item in enumerate(l2):
        l1[i] = l1[i] - item
    return l1[:]

def list_duplicates(seq):
    '''
    Returns the list duplicates of a seqeuence
    
    @return generator object listing duplicates as (key, indices)
    '''
    tally = dict()
    for i, item in enumerate(seq):
        try:
            tally[item].append(i)
        except Exception:
            tally[item] = [i]
    return ((key,locs) for key,locs in tally.items() if len(locs)>1)

def accumulate_duplicates(value_list_tuples):
    result_list = value_list_tuples[:]
    
    duplicates_by_index = list(list_duplicates([v[0] for v in value_list_tuples]))
    
    #Remove all duplicates from the list starting from highest index
    for index in sorted(reduce(add,[val[1] for val in duplicates_by_index]), reverse=True):
        result_list.pop(index)
    
    #Accumulate the duplicate entries' lists, then add to result list
    for dup in duplicates_by_index:
        to_be_added = [value_list_tuples[indices][1] for indices in dup[1]]
        accumulator = functools.reduce(list_accumulate,to_be_added)
        result_list.append((dup[0],accumulator))
        
    return result_list

def chunks(l, n):
    return [l[i:i+n] for i in range(0, len(l), n)]

def bubble_sort(lst):
    lst2 = lst[:]
    x,y = 0,0
    for x in range(0,len(lst)):
        for y in range(x,len(lst)):
            curr = lst2[x]
            next = lst2[y]
            if curr > next:
                lst2[y] = curr
                lst2[x] = next
    return lst2

def fill_arr(size):
    lst = []
    r = random.Random(time.time())
    for x in range(0,size):
        lst.append(r.randint(0,100))
    return lst

def normalize(lst):
    lst2 = lst[:]
    lst_sum = functools.reduce(lambda x, y: x+y, lst2) #map-reduce
    divisor = math.sqrt(lst_sum)
    lst2 = [elem/divisor for elem in lst2]
    return lst2

def logistic_sigmoid(x):
    return (1/( 1 + math.pow(math.e,(-1*x))))

def sigmoid(args):
    if type(args) is int or type(args) is float: #type checking
        x = args
        return (1/(1 + math.pow(0.5/x,(5))))
    elif len(args) == 3:
        x , intercept, slope = args #tuple unpacking
        return (1/( 1 + math.pow(intercept/x,slope)))
    else:
        return None #null type

def dot_product(a,b):
    if len(a) != len(b):
        return None
    s = 0
    for c in range(0,len(a)):
        s = s + (a[c] * b[c])
    return s

def floor_to_zero(val):
    return 0 if val < 0 else val

def gaussian(args):
    r = random.Random(time.time())
    if args == None:
        return r.gauss(0.0,1.0)
    elif len(args) == 2:
        mu, sigma = args
        return r.gauss(mu,sigma)
    else:
        return None
